'''
@author：fc
@date：  2021/11/10
@contact：675435108@qq.com
'''
import math
import os

import json
import random

"""
文件内容&功能简要：
# 顾名思义：该文件实现基于item的协同过滤算法，使用的数据集为ml-1m的评分数据集,给用户推荐其和其喜欢的item相似的item
"""


class ItemCF:
    def __init__(self,dataPath,ratio):
        self.dataPath = dataPath
        self.data = self.loadData()
        self.ratio = ratio
        self.train,self.test = self.splitData(3,47)
        self.itemSim = self.ItemSimilarityBest()

    def loadData(self):
        print("开始加载数据...")
        data = []
        for line in open(self.dataPath):
            userId, itemId, rate ,_ = line.split("::")
            data.append((userId,itemId,int(rate)))
        return data

    """
       拆分数据集为训练集和测试集，这里的数据集划分方式是有误的，最终划分的比例是不相等的我先弄完
    """
    def splitData(self,k,seed,M=9):
        print("训练集和测试集划分...")
        train,test = {},{}
        # M = self.dataSplitDataRatio[1]  # 随机数上限,这里的值为8
        # k = 3  # 这里的k值可以是0-8中的任意一个，为的就是划分数据集时九分之一的数据添加到测试集中
        # seed = 47  # 固定随机种子
        random.seed(seed)
        for userId,itemId,rate in self.data:
            if random.randint(0,M) == k:
                test.setdefault(userId,{})
                test[userId][itemId] = rate
            else:
                train.setdefault(userId,{})
                train[userId][itemId] = rate
        json.dump(train,open("./reml-1l/itemCFRec/train.json","w"))
        json.dump(test, open("./reml-1l/itemCFRec/test.json","w"))
        return train,test

    # 计算物品之间的相似度
    def ItemSimilarityBest(self):
        print("开始计算item之间的相似度....")
        if os.path.exists("./json/itemSim.json"):
            itemSim = json.load(open("./json/itemSim.json","r"))
            print("相似性矩阵从json文件中加载")
        else:
            itemSim = dict()  # item相似性矩阵
            item_user_count = dict()  # 存储每个item交互过的用户
            count = dict()  # 同现矩阵
            for userId, item in self.train.items():
                print("user is {}".format(userId))
                for i in item.keys():
                    item_user_count.setdefault(i,0)
                    if self.train[str(userId)][i] > 0.0:
                        item_user_count[i] += 1
                    for j in item.keys():
                        count.setdefault(i,{}).setdefault(j , 0)
                        if self.train[str(userId)][i]>0.0 and self.train[str(userId)][j]>0.0 and i != j:
                            count[i][j] += 1
            json.dump(count,open("./reml-1l/itemCFRec/count.json","w"))
            json.dump(item_user_count,open("./reml-1l/itemCFRec/item_user.count.json","w"))
            # 从同现矩阵构建相似性矩阵
            for i,related_items in count.items():
                itemSim.setdefault(i,dict())
                for j,cuv in related_items.items():
                    itemSim[i].setdefault(j,0)
                    # 我这里竟然写成count[i][i],导致相似矩阵为值0，找了一下午和一晚上的错误，细节导致弥天大错
                    # count[i][j] = cuv/math.sqrt(item_user_count[i]*item_user_count[j])
                    itemSim[i][j] = cuv/math.sqrt(item_user_count[i]*item_user_count[j])
            json.dump(itemSim,open("./json/itemSim.json","w"))
        return itemSim

    def recommend(self,user,k=8,nitems=40):
        result = dict()
        u_items = self.train.get(user,{})
        for i,pi in u_items.items():
            for j ,wj in sorted(self.itemSim[i].items(),key=lambda x:x[1],reverse=True)[0:k]:
                if j in u_items:
                    continue
                result.setdefault(j,0)
                result[j] += wj*pi
        newDict=dict(sorted(result.items(),key=lambda x:x[1],reverse=True)[0:nitems])
        return newDict

    def precision(self,k=8,nitems=10):
        print("开始计算准确率...")
        hit = 0
        precision = 0
        for user in self.test.keys():
            u_items = self.test.get(user,{})
            result = self.recommend(user,k=k,nitems=nitems)
            for item,rate in result.items():
                if item in u_items:
                    hit +=1
            precision +=nitems
        return hit/(precision*1.0)



if __name__ == '__main__':
    dataPath = "./data/ml-1m/ratings.dat"
    dataSplitRatio = [1,9]   # 数据划分比例
    itemCF = ItemCF(dataPath,dataSplitRatio)
    print("user 1 进行推荐的结果如下：{}".format(itemCF.recommend("1")))
    print("准确率为：{}".format(itemCF.precision()))